﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Proggy.Web.Cms.Context;
using Proggy.Web.Cms.Cache;
using System.ComponentModel.DataAnnotations;
using Newtonsoft.Json;
using Proggy.Web.Cms.Repositories.ContentModel;
using Proggy.Web.Cms.Service;
using Proggy.Web.Cms.Controllers.FrontEnd;
using Proggy.Web.Cms.Repositories.Published;
using Ninject;
using Proggy.Web.Cms.Membership;
using System.Web.Mvc;
using Proggy.Web.Cms.Repositories.Membership;

namespace Proggy.Web.Cms.ContentModel
{
    /// <summary>
    /// Content model base
    /// </summary>
    public class ContentModelBase : IContentModelBase
    {
        /// <summary>
        /// Testing constructor
        /// </summary>
        /// <param name="repo"></param>
        [JsonConstructor]
        public ContentModelBase(IContentModelTypeRepository cmRepo, IMembershipRepository membRepo)
        {
            this._cmtRepo = cmRepo;
            this._membRepo = membRepo;
        }

        public ContentModelBase()
        {
            try
            {
                this._cmtRepo = DependencyResolver.Current.GetService<IContentModelTypeRepository>();
                this._membRepo = DependencyResolver.Current.GetService<IMembershipRepository>();
            }
            catch (Exception ex)
            {
                throw new Exception("Cannot use parameterless constructor here. DependencyResolver.Current.GetService<T> is throwing an error: " + ex.Message);
            }
        }

        ///// <summary>
        ///// Parameterless constructor
        ///// </summary>
        //public ContentModelBase()
        //{
        //    this.ContentModelTypeRepository = DependencyResolver.Current.GetService<IContentModelTypeRepository>();
        //}

        /// <summary>
        /// Content Id
        /// </summary>
        public int Id
        {
            get;
            set;
        }

        /// <summary>
        /// An alias, which has to be unique and is mandatory but user inputted
        /// </summary>
        [MaxLength(64, ErrorMessage = "Unique alias cannot be longer than 64 characters in length")]
        [MinLength(6, ErrorMessage = "Unique alias is optional, however if it entered, it must be at least 6 characters long")]
        [RegularExpression("^[a-zA-Z0-9_]*$", ErrorMessage = "Unique alias can only contain uppercase letters, numbers and underscores (_)")]
        [Display(Name = "Unique alias")]
        public string UniqueAlias { get; set; }

        /// <summary>
        /// Content name
        /// </summary>  
        [MaxLength(100)]
        [Display(Name = "Content name")]
        [Required(AllowEmptyStrings = false)]
        [MinLength(1)]
        [JsonIgnore]
        public string Name
        {
            get;
            set;
        }

        /// <summary>
        /// Content url
        /// </summary>
        [JsonIgnore]
        public string Url
        {
            get
            {

                if (UrlCache.CachedNodeUrls == null)
                {
                    return null;
                }
                else
                {

                    // get cached url object
                    var cached = UrlCache.CachedNodeUrls.SingleOrDefault(x => x.NodeId == this.Id);

                    // check
                    if (cached == null)
                    {
                        return null;
                    }

                    return cached.NodeUrl;
                }
            }
        }

        /// <summary>
        /// Content Url slug
        /// </summary>    
        [MaxLength(50)]
        [RegularExpression("^[a-z0-9-]*$", ErrorMessage = "Url can only contain lowercase letters, numbers and dashes (-)")]
        [Display(Name = "Url slug")]
        [Required(AllowEmptyStrings = false)]
        [JsonIgnore]
        public string UrlSlug
        {
            get;
            set;
        }

        /// <summary>
        /// Template string
        /// </summary>
        [JsonIgnore]
        public string Template { get; set; }

        /// <summary>
        /// Content model type alias
        /// </summary>
        [JsonIgnore]
        public string TypeAlias { get; set; }

        /// <summary>
        /// Parent Id (if exists)
        /// </summary>
        [JsonIgnore]
        public int? ParentId
        {
            get;
            set;
        }

        /// <summary>
        /// Order in parent container (or 0)
        /// </summary>
        [JsonIgnore]
        public int OrderInParent
        {
            get;
            set;
        }

        /// <summary>
        /// First created
        /// </summary>
        [JsonIgnore]
        public DateTime FirstCreated
        {
            get;
            set;
        }

        /// <summary>
        /// Last updated
        /// </summary>
        [JsonIgnore]
        public DateTime LastUpdated
        {
            get;
            set;
        }

        /// <summary>
        /// First created
        /// </summary>
        [JsonIgnore]
        public ProggyMember FirstCreatedMember
        {
            get
            {
                if (_firstCreatedMember == null)
                {
                    this._firstCreatedMember = this._membRepo.GetMember(this.FirstCreatedMemberUsername, false);
                }
                return _firstCreatedMember;
            }
        }
        private ProggyMember _firstCreatedMember;

        [JsonIgnore]
        public string FirstCreatedMemberUsername { get; set; }

        /// <summary>
        /// Last updated
        /// </summary>
        [JsonIgnore]
        public ProggyMember LastUpdatedMember
        {
            get
            {
                if (_lastUpdatedMember == null)
                {
                    _lastUpdatedMember = this._membRepo.GetMember(this.LastUpdatedMemberUsername, false);
                }
                return _lastUpdatedMember;
            }
        }
        private ProggyMember _lastUpdatedMember;

        [JsonIgnore]
        public string LastUpdatedMemberUsername { get; set; }

        /// <summary>
        /// Get children of type IContentModel
        /// </summary>
        /// <returns></returns>
        public IEnumerable<IContentModel> GetAbstractChildren()
        {
            return this.PublishedContentRepository.PublishedContent.Where(x =>
                x.ParentId == this.Id).OrderBy(x => x.OrderInParent);
        }

        /// <summary>
        /// Get strongly typed child content
        /// </summary>
        /// <typeparam name="TContentModel"></typeparam>
        /// <returns></returns>
        public IEnumerable<TContentModel> GetTypedChildren<TContentModel>()
        {
            // get type
            var type = typeof(TContentModel);

            //
            return
                this.GetAbstractChildren()
                    .Where(x => x.GetType() == typeof(TContentModel))
                    .Select(x => (TContentModel)x);

        }

        /// <summary>
        /// Return parent as IContentModel
        /// </summary>
        /// <returns></returns>
        public IContentModel GetAbstractParent()
        {
            // check parent id
            if (this.ParentId != null)
            {
                return this.PublishedContentRepository.PublishedContent.Single(x => x.Id == (int)this.ParentId);
            }

            return null;
        }

        /// <summary>
        /// Return parent strongly typed
        /// </summary>
        /// <typeparam name="TContentModel"></typeparam>
        /// <returns></returns>
        public TContentModel GetTypedParent<TContentModel>()
        {
            // get parent
            var parent = this.GetAbstractParent();

            // check
            if (parent != null)
            {
                return (TContentModel)parent;
            }

            //
            throw new KeyNotFoundException("ParentNodeId");
        }

        /// <summary>
        /// Returns JSON object representation for this model
        /// </summary>
        /// <returns></returns>
        public virtual object GetJsonObject()
        {
            return new
            {
                Name = this.Name,
                Url = this.Url
            };
        }

        /// <summary>
        /// Get type object for this model
        /// </summary>
        /// <returns></returns>
        public AvailableContentModel GetTypeData()
        {
            // get type
            var type = this.GetType();

            //
            return this._cmtRepo.AvailableContentModels.Single(x => x.Type == type);
        }

        /// <summary>
        /// True if root content
        /// </summary>
        [JsonIgnore]
        public bool IsRoot
        {
            get;
            set;
        }

        /// <summary>
        /// Does nothing here - for overriding
        /// </summary>
        /// <param name="repo"></param>
        public virtual void OnAfterModelBinding(IContentCrudService repo)
        {

        }

        /// <summary>
        /// Does nothing here - for overriding
        /// </summary>
        /// <param name="repo"></param>
        public virtual void OnAfterSuccesfulSave(IContentCrudService repo)
        {

        }

        /// <summary>
        /// After successful publish
        /// </summary>
        /// <param name="repo"></param>
        /// <param name="contentModel"></param>
        public virtual void OnAfterSuccessfulPublish(IContentCrudService repo)
        {

        }

        /// <summary>
        /// After successful trash
        /// </summary>
        /// <param name="repo"></param>
        public virtual void OnAfterSuccessfulTrash(IContentCrudService repo)
        {

        }

        /// <summary>
        /// After succesful move
        /// </summary>
        /// <param name="repo"></param>
        public virtual void OnAfterSuccessfulMove(IContentCrudService repo)
        {

        }

        /// <summary>
        /// After successful sort
        /// </summary>
        /// <param name="repo"></param>
        public virtual void OnAfterSuccessfulSort(IContentCrudService repo)
        {

        }

        /// <summary>
        /// Returns the relevant controller for this content
        /// </summary>
        /// <returns></returns>
        public FrontEndControllerBase GetControllerForContent()
        {
            // get type data
            var typeData = this.GetTypeData();

            // get type
            var type = Type.GetType(typeData.ControllerNamespace + "." + this.TypeAlias + "Controller," + typeData.AssemblyName);

            //
            return (FrontEndControllerBase)Activator.CreateInstance(type);
        }

        private IContentModelTypeRepository _cmtRepo;
        private IMembershipRepository _membRepo;

        /// <summary>
        /// True if parent id != null
        /// </summary>
        [JsonIgnore]
        public bool HasParent
        {
            get { return this.ParentId != null; }
        }

        /// <summary>
        /// Published Content Repository
        /// </summary>
        [JsonIgnore]
        public IPublishedContentRepository PublishedContentRepository
        {
            get
            {
                if (this._publishedRepo == null)
                {
                    try
                    {
                        this._publishedRepo = DependencyResolver.Current.GetService<IPublishedContentRepository>();
                    }
                    catch(Exception ex)
                    {
                        throw new Exception("Cannot create instance of IPublishedContentRepository: " + ex.Message + " - set this property (ContentModelBase.PublishedContentRepository) manually using it's setter");
                    }
                }

                return this._publishedRepo;
            }
            set
            {
                this._publishedRepo = value;
            }

        }
        private IPublishedContentRepository _publishedRepo;

        /// <summary>
        /// Level in tree
        /// </summary>
        public int LeveInTree
        {
            get;
            set;
        }
    }
}
